'''
sqli.py

Copyright 2009 Xavier Mendez Navarro aka Javi

This file is part of pysqlin

pysqlin is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation version 2 of the License.

pysqlin is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with pysqlin; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
'''

import logging
import re

from framework.settings import Settings
from framework.queries import Query
from framework.mymodel import InjectionData
from framework.interfaces import IPlugin
from framework.baseclass import BPlugin
from framework.roles import Registrant
from framework.facade import Facade

import xml.dom.minidom
from xml.dom.minidom import Node

class Sqli(BPlugin, IPlugin):
    """Launchs and controls the injection threads"""

    def __init__(self, api):
	BPlugin.__init__(self, api)

	self.__logger = logging.getLogger('plugins.auxiliary.sql.sqli')
	self.module = None

    def name(self): return "SQL injector"
    def description(self): 
	return """
	Performs SQL Injection queries. 

	* It uses a database in XML with the most frequent queries.
	* You can edit injections.xml to add your favorite queries.
	* All the fetched data is stored in a knowledge base.
	* If your system has readline it will auto-complete every table, column... when they are used in other queries as they are fetched.
	"""

    def parameters(self): return {}

    def commands(self): 
	return (
	    ('rawquery', 'Performs a raw sql injection query.', 'rawquery <query>', [('query',(), None)], self.cmd_raw),
	    ('query_set', 'Sets SQL queries set', 'query_set <module>', [('module',('{@event@}',), None)], self.cmd_query_set),
	)

    def init(self): 
	self.queries = Queries()
	data = self.get_session_data()

	if data[InjectionData.DATABASE] in self.queries.get_databases():
	    self.module = data[InjectionData.DATABASE]
	    self.load_query_set(self.module)

	self.subscribe(self.evt_get_modules, 'PARAMETER query_set.module', True)

    def load_query_set(self, set):
	for name, query in self.queries.get_queries(set):
	    Facade().add_command(name, query.help, query.usage,
		query.params,
		self.cmd_prepare_injetion )

    def cmd_query_set(self, **event):
	module = event['parameters']['module']
	if not self.module in self.queries.get_databases():
	    return "Module does not exists"

	self.module = module
	self.load_query_set(self.module)

    def cmd_raw(self, **event):
	    for word in self.make_injection(Query(select=event['parameters']['query'])):
		self.print_msg(word)

    def cmd_prepare_injetion(self, **event):
	self.__logger.debug('START cmd_prepare_injetion, param: %s', event)

	if self.module is None: return

	word = None

	try:
	    query = self.queries.get_query(self.module, event['cmd'])
	except KeyError:
	    self.__logger.error('prepare_injetion. EXCEPTION: command name must be equal to injecion name!')
	    return

	if event['parameters'].has_key('row') and event['parameters']['row'].count('-') == 1:
	    from_row, to_row = event['parameters']['row'].split('-')

	    if not to_row: to_row = 9999

	    try:
		from_row = int(from_row) 
		to_row = int(to_row)
	    except ValueError:
		self.__logger.error('prepare_injetion. EXCEPTION: from_row, to_row not numerics')
		return "Invalid numeric range"

	    self.__logger.debug('cmd_prepare_injetion. from_row=%d, to_row=%d' % (from_row, to_row))

	    if from_row < to_row:
		params = event['parameters']
		del(params['row'])
		for word in self.make_injection(query.expand(params), from_row, to_row):
		    self.print_msg(word)
	else:
	    for word in self.make_injection(query.expand(event['parameters'])):
		self.print_msg(word)

	if word is None:
	    return 'Data not found!'

    def evt_get_modules(self, **event):
	return self.queries.get_databases()

class PluginQuery(Query):
    def __init__(self, node):
	Query.__init__(self, None, None, None, [], '')

	self.help = ''
	self.usage = ''
	self.params = []
	self.__parse_xml(node)

    # ------------------------------------------------
    # XML parsing
    # ------------------------------------------------
    def __get_text(self, el):
	rc = ""
	for node in el.childNodes:
	    if node.nodeType == node.TEXT_NODE:
		rc = rc + node.data
	return rc.strip()

    def __parse_xml(self, node):
	self.name = node.getAttribute("name")
	self.select = self.__get_text(node.getElementsByTagName("select")[0])
	if node.getElementsByTagName("charset"):
	    charset_str = self.__get_text(node.getElementsByTagName("charset")[0])
	    self.set_charset(charset_str)

	if node.getElementsByTagName("split"):
	    self.split_string = self.__get_text(node.getElementsByTagName("split")[0])

	if node.getElementsByTagName("help"):
	    self.help = self.__get_text(node.getElementsByTagName("help")[0])

	if node.getElementsByTagName("usage"):
	    self.usage = self.__get_text(node.getElementsByTagName("usage")[0])

	for l in node.getElementsByTagName("knowledge"):
	    self.kbase_field.append(self.__get_text(l))

	for nodeparams in node.getElementsByTagName("param"):
	    name = nodeparams.getAttribute("name")
	    default = nodeparams.getAttribute("default")

	    lvalues = []
	    for nodevalues in nodeparams.getElementsByTagName("value"):
		t = self.__get_text(nodevalues)
		lvalues.append(t)
	    self.params.append((name, lvalues, default))

class Queries:
    def __init__(self):
	self.__logger = logging.getLogger("framework.Queries")

	self.data = {}
	#filename = Settings().get(Settings.SEC_GRL, Settings.QUERIES_FILE),
	self.__parse_xml('injections.xml')

    # ------------------------------------------------
    # XML parsing
    # ------------------------------------------------
    def __parse_xml(self, filename):
	self.__logger.debug('__parse_xml. START, file=%s' % (filename,))

	doc = xml.dom.minidom.parse(filename)

	for node in doc.getElementsByTagName("database"):
	    database_name = node.getAttribute("name")

	    for qnode in node.getElementsByTagName("query"):
		self.__add_query(database_name, PluginQuery(qnode))

    # ------------------------------------------------
    # data functions
    # ------------------------------------------------
    def __add_query(self, database, query):
	if not self.data.has_key(database):
	    self.data[database] = {}

	self.data[database][query.name] = query

    def get_databases(self):
	return self.data.keys()

    def get_queries(self, database):
	return self.data[database].items()

    def get_queries_name(self, database):
	return self.data[database].keys()

    def set_query(self, database, name, value):
	self.data[database][name].select = value

    def get_query(self, database, name):
	self.__logger.debug('START get_query, db=%s,name=%s' % (database, name))

	return self.data[database][name]

def load(api):
    return Sqli(api)
